
<!DOCTYPE html>
<html>
	
<head>
	<style>
		
	.bdbox{
		--border: solid 3px red;
		--min-height: 100%;
		width: 100%;
	}
		
	.cardbox {
	  border: solid thin silver;
	  width: 99%;
	  min-height: 120px;
	}
	.code-block{
	  border: solid thin silver;
	  width: 99%;
	  min-height: 64px;
	  max-height: 120px;
	  overflow-y: scroll;
	}
	.divline{
	  color: black;
	  width: 99%;
	  text-align: left;
	}
	.info{
	  color: green;
	}
	
	.imdbg{
	  --background: url('@/assets/vue.svg') no-repeat center;
	  --background-size: 100% 100%;
	  width: 64px;
	  height: 64px;
	}
	.scrolly{
		overflow-y: scroll;
		max-height: 300px;
	}
	
	</style>
	
<script>
	
function testES6(){
	let objA ={a:'aaa'};
	console.log('objA:',objA);
	let etxt1=document.getElementById('txt1');
	let stxt1 = etxt1.innerText ? etxt1.innerText :'';
	stxt1 = stxt1 +'\n objA:'+JSON.stringify(objA);
	etxt1.innerText = stxt1; 
	console.log('stxt1:',stxt1);
	
	let val1 = objA.b?.txt; //属性b如果未定义不会报错且返回undefined，有定义则取b.txt
	console.log('objA.b?.txt:',val1);
	let val2 = val1 ?? '未定义'; //如果前面的变量未定义则取??后面的值
	console.log('val2:',val2);
	if (!val2) val2 = '(undefined)';
	let stxt2 = etxt1.innerText;
	if (!stxt2) stxt2 = '(innerText:undefined)';
	console.log('stxt2:'+stxt2);
	stxt2 = stxt2 +'\n val2:'+val2;
	etxt1.innerText = stxt2;
	console.log('stxt2-end:',stxt2);
    
}

function clearInfo(){
	let etxt1=document.getElementById('txt1');
	let stxt1;
	etxt1.innerText = stxt1 ?? '(??清除)'; 
}

const on_load_hdr = (event)=>{
	//console.log('window.onload,event:',event);
	//console.log('window.onload,window:',window);
	let sinfo = window.navigator.userAgent;
	let e_verspan =document.getElementById('e_verspan');
	e_verspan.innerHTML = sinfo;
	
};

window.onload = on_load_hdr;

function testType(){

    //判断变量类型：
    console.log('判断变量类型:');
    let x1 = "abcdefg"; 
    let x2 = {};
    let x3 = {a:'abc'};
    
    let x4 = new Map();
    let x1xt1 = x1 instanceof String;
    let x1xt2 = x1 instanceof Object;
    console.log('x1 instanceof String:',x1xt1,'x1 instanceof Object',x1xt2);
    console.log('typeof(x1):',typeof(x1));

    let x2xt2 = x2 instanceof Object;
    console.log('x2 instanceof Object',x2xt2);
    console.log('typeof(x2):',typeof(x2));

    let x3xt2 = x3 instanceof Object;
    console.log('x3 instanceof Object',x3xt2);
    console.log('typeof(x3):',typeof(x3));
    
    let x4xt2 = x4 instanceof Object;
    console.log('x4 instanceof Object',x4xt2);
    console.log('typeof(x4):',typeof(x4));
	
    //JS 判断数据类型的 8 种方式
    //1、typeof 只能识别基础类型和引用类型,注意：null、 NaN、 document.all 的判断
    console.log('typeof 123:',typeof 123); // "number"
    console.log('typeof "hello":',typeof "hello"); // "string"
    console.log('typeof true:',typeof true); // "boolean"
    console.log('typeof undefined:',typeof undefined); // "undefined"
    console.log('typeof null:',typeof null); // "object"
    console.log('typeof {}:',typeof {}); // "object"
    console.log('typeof []:',typeof []); // "object"
    console.log('typeof function(){}:',typeof function(){}); // "function"
    console.log('typeof NaN:',typeof NaN); // "number"
    console.log('typeof document.all:',typeof document.all); // "undefined"

    //2、constructor constructor 指向创建该实例对象的构造函数,
    //注意 null 和 undefined 没有 constructor，以及 constructor 可以被改写，不太可靠
    const arr = [1, 2, 3];
    console.log('arr.constructor === Array:',arr.constructor === Array) // true

    const obj = {name: "XYZ", age: 18};
    console.log('obj.constructor === Object:',obj.constructor === Object) // true

    //改写后constructor被坏了
    String.prototype.constructor = function fn() {
      return {}; 
    }

    // constructor 可以被改写
    console.log('"XXX".constructor:',"XXX".constructor) // [Function: fn]
      
    //3、instanceof 
    //语法：obj instanceof Type
    //功能：判断 obj 是不是 Type 类的实例，只可用来判断引用数据
    //实现思路： Type 的原型对象是否是 obj 的原型链上的某个对象
    //注意：右操作数必须是函数或者 class
    //const arr = [1, 2, 3]
    console.log('arr instanceof Array:',arr instanceof Array) // true
    console.log('arr instanceof Object:',arr instanceof Object) // true

    //const obj = { name: "XYZ", age: 18 }
    console.log('obj instanceof Object:',obj instanceof Object) // true
    console.log('obj instanceof Array:',obj instanceof Array) // false
    
    //4、isXXX
    /*
    isPrototypeof
    用于判断一个对象是否为另一个对象的原型
    prototypeObj.isPrototypeOf(object)，如果 prototypeObj 是 object 的原型对象，isPrototypeOf 方法返回 true，否则返回 false
    功能基本等同于 instanceof
    注意：isPrototypeOf 方法只能用于判断对象类型，不能用于判断基本数据类型。如果 prototypeObj 不是一个对象，isPrototypeOf 方法会抛出 TypeError 异常
    getPrototypeOf 返回一个对象的原型，只能用于判断对象类型
    */
   
    const proto1 = Object.getPrototypeOf(obj)
    console.log('proto1.isPrototypeOf(obj):',proto1.isPrototypeOf(obj)) // true

    const proto2 = Object.getPrototypeOf(arr)
    console.log('proto2.isPrototypeOf(arr):',proto2.isPrototypeOf(arr)) // true

    console.log(Object.isPrototypeOf({})) // false
    console.log(Object.prototype.isPrototypeOf({})) // true  期望左操作数是一个原型，{} 原型链能找到 Object.prototype

    console.log('Object.getPrototypeOf(obj):',Object.getPrototypeOf(obj)) 
    console.log(Object.getPrototypeOf(obj) === Object.prototype) // true
    console.log('Object.getPrototypeOf(arr):',Object.getPrototypeOf(arr))
    console.log(Object.getPrototypeOf(arr) === Array.prototype) // true

    // Array.isArray 方法可以判断一个对象是否为数组
    // Number.isNaN 可以判断一个值是否为 NaN
    // Number.isFinite 可以判断一个值是否为有限数
    
    //5、Object.prototype.toString , 利用函数动态 this 的特性
    console.log('5、Object.prototype.toString:')
    let callType;
    callType=Object.prototype.toString.call(123); // "[object Number]"
    console.log('call(123):',callType);    
    callType=Object.prototype.toString.call("hello"); // "[object String]"
    console.log('call("hello"):',callType);
    callType=Object.prototype.toString.call(true); // "[object Boolean]"
    console.log('call(true):',callType);    
    callType=Object.prototype.toString.call(undefined); // "[object Undefined]"
    console.log('call(undefined):',callType);    
    callType=Object.prototype.toString.call(null); // "[object Null]"
    console.log('call(null):',callType);    
    callType=Object.prototype.toString.call({}); // "[object Object]"
    console.log('call({}):',callType);    
    callType=Object.prototype.toString.call([]); // "[object Array]"
    console.log('call([]):',callType);    
    callType=Object.prototype.toString.call(function(){}); // "[object Function]"
    console.log('call(function(){}):',callType);    
    // 注意的是，Object.prototype.toString.call 方法返回的字符串格式为 "[object 类型]"
    //异常类:
    try{
       throw 'made err!'; //异常抛出的内容可能是string,也可能是object
    }catch(err){
       console.log('err:',err);    
       callType=Object.prototype.toString.call(err); 
       console.log('call(err):',callType);    
    }
    
    //6、鸭子类型检测
    console.log('6、鸭子类型检测: 如果一个东西表现出像鸭子，那就是鸭子.')
    /*
    检查自身属性的类型或者执行结果的类型
    通常作为候选方案
    例子：kindof 与 p-is-promise    
    */
    //p-is-promise：
   
    const isObject1 = value =>
      value !== null && (typeof value === "object" || typeof value === "function");

    const pIsPromise = value =>{
      return (
        value instanceof Promise ||
        (isObject(value) && typeof value.then === "function" && typeof value.catch === "function")
      );
    };
    
    //kindof：
    const kindof = function kindof(obj) {
      var type;
      if (obj === undefined) return "undefined";
      if (obj === null) return "null";

      switch ((type = typeof obj)) {
        case "object":
          switch (Object.prototype.toString.call(obj)) {
            case "[object RegExp]":
              return "regexp";
            case "[object Date]":
              return "date";
            case "[object Array]":
              return "array";
          }

        default:
          return type;
      }
    };

    //7、Symbol.toStringTag
    console.log('7、Symbol.toStringTag:')
    /*
    原理：Object.prototype.toString 会读取该值
    适用场景：需自定义类型
    注意事项：兼容性  
    */    
   
    console.log('toString.call(arr):',Object.prototype.toString.call(arr)); // [object MyArray]
   
    class MyArray {
      get [Symbol.toStringTag]() {
        return "MyArray";
      }
    }

    const myarr = new MyArray();
    console.log('toString.call(myarr):',Object.prototype.toString.call(myarr)); // [object MyArray]

    console.log('8、等比较:');
    /*
     8、等比较
     原理：与某个固定值进行比较
     适用场景：undefined、 window、 document、 null 等
     underscore.js（工具函数库）：  
    */
    const underscoreJS = function (){
        let _ = this;
        _.isNull = function(obj) {
          return obj === null;
        };

        _.isUndefined = function(obj) {
          return obj === void 0;
        };

        _.isBoolean = function(obj) {
          return obj === true || obj === false || toString.call(obj) === '[object Boolean]';
        };

        _.isString = function(obj) {
          return toString.call(obj) === '[object String]';
        };

        _.isNumber = function(obj) {
          return toString.call(obj) === '[object Number]';
        };

        _.isArray = function(obj) {
          return toString.call(obj) === '[object Array]';
        };

        _.isObject = function(obj) {
          var type = typeof obj;
          return type === 'function' || type === 'object' && !!obj;
        };

        _.isFunction = function(obj) {
          return typeof obj === 'function' || false;
        };
        return _;
    };
    
    console.log('underscoreJS:',underscoreJS);
    const underscore = underscoreJS();
    console.log('underscore:',underscore);
    let visnull = underscore.isNull(null);
    console.log('visnull:',visnull);
    let visobj = underscore.isObject(obj);
    console.log('obj:',obj);
    console.log('visobj:',visobj);
    
    /*
    void 0 始终返回 undefined，void 后面接任意值都是返回 undefined， 
    这是为了兼容 IE，因为在 IE 中 undefined 值可以被改写   
     
    总结
    方法          基础数据类型      引用类型    注意事项
    typeof         √                 ×        NaN、object、document.all
    constructor    √部分             √        可以被改写
    instanceof     ×                 √        多窗口，右边构造函数或者class
    isPrototypeof  ×                 √        小心 null 和 undefined
    toString       √                 √        小心内置原型
    鸭子类型        -                 √        不得已兼容
    Symbol.toString Tag x            √        识别自定义对象
    等比较         √                  √        特殊对象
    */    
   
    /*
    ES6 增强的 NaN
    NaN 和 Number.NaN 特点
        typeof 判断类型是数字
        自己不等于自己
        最好用 Number.isNaN 来判断。
    */
    console.log('isNaN(NaN):',isNaN(NaN)) // true
    console.log('Number.isNaN(NaN):',Number.isNaN(NaN)) // true
    console.log('isNaN({}):',isNaN({})) // true   
    console.log('Number.isNaN(1e12):',Number.isNaN(1e12)) // true
    
    /*
    indexOf 和 includes
    indexOf 不可查找 NaN，includes 则可以
    const arr9 = [NaN]
    console.log(arr9.indexOf(NaN)) // -1
    console.log(arr9.includes(NaN)) // true    
    */
   
}

function tryParse(exp){
    let step = 'beg';
    let isok = false;
    try{
        console.log('exp:',exp);
        let result;
        if (exp.startsWith('{') && exp.endsWith('}')){
            step = 'JSON.parse';
            result = JSON.parse(exp);
            console.log('exp as JSON:',result);
        }else{
            step = 'eval/1';
            result = eval(exp);
            console.log('exp as express, result:',result);
        };
        isok = true;
    }catch(err){
      console.log('err1,step:'+step+':',err.message || err);
    }; 
    if (!isok)
    try{
      step = 'eval/2';
      let result = eval(exp);
      console.log('2-eval(exp):',result);
      isok = true;
    }catch(err){
      console.log('err2,step:'+step+':',err.message || err);
    }; 
    /*
    if (!isok)
    try{
      step = 'eval/3';
      let result = eval('('+exp+')');
      console.log('3-eval((exp)):',result);
      isok = true;
    }catch(err){
      console.log('err3,step:'+step+':',err.message || err);
    };     
    */
}

function testJSON_eval(){
    let exp1 = '123+456';
    let exp2 = '{"F1":"f1val"}'; //标准的JSON格式或以 JSON.parse
    let exp3 = '()=>{ return 123}';
    let exp4 = '{F2:"f2val"}'; 
    let exp5 = '{F2:“f2val”,F3:“f3val”}'; //不是标准的JSON格式，只能 eval('('+exp5+')')
    tryParse(exp1); //OK 579
    tryParse(exp2); //OK JSON
    tryParse('('+exp2+')'); //OK eval结果：{F1: 'f1val'}
    tryParse(exp3); //OK funcrion
    tryParse(exp4); //JSON err,eval 直接得到结果 f2val
    tryParse('('+exp4+')'); //eval 得到对象 {F2: 'f2val'}
    tryParse(exp5); //JSON err,eval err Unexpected token ':'
    let exp6 = '{\'F2\':\'f2val\',\'F3\':\'f3val\'}'; //非标准的JSON格式，只能 eval('('+exp6+')')
    tryParse(exp6); //JSON err,eval err Unexpected token ':'
    tryParse('('+exp6+')'); //eval 得到对象 {F2: 'f2val', F3: 'f3val'}
    tryParse('(('+exp6+'))'); //eval 得到对象 {F2: 'f2val', F3: 'f3val'}
    
}

</script>
	
</head>	
  <div class="bdbox">
 
	  <div class="divline">
		<h1>测试ES6扩展语法的支持</h1>
	  </div>
	  <div class="code-block">
		<code class="">
		let objA ={a:'aaa'}; <br/>
		let val1 = objA.b?.txt; //低版本浏览器报错 <br/>
		let val2 = val1 ?? '未定义';  //低版本浏览器报错 <br/>
		
	    </code>
	  </div>
	  
	  <div class="cardbox">
		<div class="divline">
		  <span>当前浏览器版本：</span>
		  <span id='e_verspan'> </span>
		</div>
		<input type="button" onclick="testES6()" value="测试ES6扩展语法的支持" />
		<input type="button" onclick="clearInfo()" value="clearInfo" />
        <input type="button" onclick="testType()" value="检测变量类型" />
        <input type="button" onclick="testJSON_eval()" value="JSON/eval" />
	  </div>
	  <div id="txt1" class="cardbox scrolly">
		<!-- <textarea id="txt1" class="cardbox">内容</textarea> -->

	  </div>

  </div>
</html>



